package com.itaming.lycheeframework.support.utils;

import lombok.experimental.UtilityClass;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

/**
 * Digest工具类
 *
 * @author A.Ming
 */
@UtilityClass
public class DigestUtil {

    static {
        // 添加 Bouncy Castle 加密提供程序，扩展 "SM3"、"HmacSM3"
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 摘要算法
     */
    private static final String ALGORITHM_MD5 = "MD5";
    private static final String ALGORITHM_SHA1 = "SHA-1";
    private static final String ALGORITHM_SHA224 = "SHA-224";
    private static final String ALGORITHM_SHA256 = "SHA-256";
    private static final String ALGORITHM_SHA384 = "SHA-384";
    private static final String ALGORITHM_SHA512 = "SHA-512";
    private static final String ALGORITHM_SM3 = "SM3";
    private static final String ALGORITHM_HMAC_MD5 = "HmacMD5";
    private static final String ALGORITHM_HMAC_SHA1 = "HmacSHA1";
    private static final String ALGORITHM_HMAC_SHA224 = "HmacSHA224";
    private static final String ALGORITHM_HMAC_SHA256 = "HmacSHA256";
    private static final String ALGORITHM_HMAC_SHA384 = "HmacSHA384";
    private static final String ALGORITHM_HMAC_SHA512 = "HmacSHA512";
    private static final String ALGORITHM_HMAC_SM3 = "HmacSM3";

    /**
     * MD5
     *
     * @param data 数据
     * @return MD5摘要字符串
     */
    public static String md5(String data) {
        return md5(data, false);
    }

    /**
     * MD5摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return MD5摘要字符串
     */
    public static String md5(String data, boolean toUpperCase) {
        return md5(StringUtil.getBytes(data), toUpperCase);
    }

    /**
     * MD5摘要
     *
     * @param data 数据
     * @return MD5摘要字符串
     */
    public static String md5(byte[] data) {
        return md5(data, false);
    }

    /**
     * MD5摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return MD5摘要字符串
     */
    public static String md5(byte[] data, boolean toUpperCase) {
        return digestHexString(ALGORITHM_MD5, data, toUpperCase);
    }

    /**
     * SHA-1摘要
     *
     * @param data 数据
     * @return SHA-1摘要字符串
     */
    public static String sha1(String data) {
        return sha1(data, false);
    }

    /**
     * SHA-1摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-1摘要字符串
     */
    public static String sha1(String data, boolean toUpperCase) {
        return sha1(StringUtil.getBytes(data), toUpperCase);
    }

    /**
     * SHA-1摘要
     *
     * @param data 数据
     * @return SHA-1摘要字符串
     */
    public static String sha1(byte[] data) {
        return sha1(data, false);
    }

    /**
     * SHA-1摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-1摘要字符串
     */
    public static String sha1(byte[] data, boolean toUpperCase) {
        return digestHexString(ALGORITHM_SHA1, data, toUpperCase);
    }

    /**
     * SHA-224摘要
     *
     * @param data 数据
     * @return SHA-224摘要字符串
     */
    public static String sha224(String data) {
        return sha224(data, false);
    }

    /**
     * SHA-224摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-224摘要字符串
     */
    public static String sha224(String data, boolean toUpperCase) {
        return sha224(StringUtil.getBytes(data), toUpperCase);
    }

    /**
     * SHA-224摘要
     *
     * @param data 数据
     * @return SHA-224摘要字符串
     */
    public static String sha224(byte[] data) {
        return sha224(data, false);
    }

    /**
     * SHA-224摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-224摘要字符串
     */
    public static String sha224(byte[] data, boolean toUpperCase) {
        return digestHexString(ALGORITHM_SHA224, data, toUpperCase);
    }

    /**
     * SHA-256摘要
     *
     * @param data 数据
     * @return SHA-256摘要字符串
     */
    public static String sha256(String data) {
        return sha256(data, false);
    }

    /**
     * SHA-256摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-256摘要字符串
     */
    public static String sha256(String data, boolean toUpperCase) {
        return sha256(StringUtil.getBytes(data), toUpperCase);
    }

    /**
     * SHA-256摘要
     *
     * @param data 数据
     * @return SHA-256摘要字符串
     */
    public static String sha256(byte[] data) {
        return sha256(data, false);
    }

    /**
     * SHA-256摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-256摘要字符串
     */
    public static String sha256(byte[] data, boolean toUpperCase) {
        return digestHexString(ALGORITHM_SHA256, data, toUpperCase);
    }

    /**
     * SHA-384摘要
     *
     * @param data 数据
     * @return SHA-384摘要字符串
     */
    public static String sha384(String data) {
        return sha384(data, false);
    }

    /**
     * SHA-384摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-384摘要字符串
     */
    public static String sha384(String data, boolean toUpperCase) {
        return sha384(StringUtil.getBytes(data), toUpperCase);
    }

    /**
     * SHA-384摘要
     *
     * @param data 数据
     * @return SHA-384摘要字符串
     */
    public static String sha384(byte[] data) {
        return sha384(data, false);
    }

    /**
     * SHA-384摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-384摘要字符串
     */
    public static String sha384(byte[] data, boolean toUpperCase) {
        return digestHexString(ALGORITHM_SHA384, data, toUpperCase);
    }

    /**
     * SHA-512摘要
     *
     * @param data 数据
     * @return SHA-512摘要字符串
     */
    public static String sha512(String data) {
        return sha512(data, false);
    }

    /**
     * SHA-512摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-512摘要字符串
     */
    public static String sha512(String data, boolean toUpperCase) {
        return sha512(StringUtil.getBytes(data), toUpperCase);
    }

    /**
     * SHA-512摘要
     *
     * @param data 数据
     * @return SHA-512摘要字符串
     */
    public static String sha512(byte[] data) {
        return sha512(data, false);
    }

    /**
     * SHA-512摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SHA-512摘要字符串
     */
    public static String sha512(byte[] data, boolean toUpperCase) {
        return digestHexString(ALGORITHM_SHA512, data, toUpperCase);
    }

    /**
     * SM3摘要
     *
     * @param data 数据
     * @return SM3摘要字符串
     */
    public static String sm3(String data) {
        return sm3(data, false);
    }

    /**
     * SM3摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SM3摘要字符串
     */
    public static String sm3(String data, boolean toUpperCase) {
        return sm3(StringUtil.getBytes(data), toUpperCase);
    }

    /**
     * SM3摘要
     *
     * @param data 数据
     * @return SM3摘要字符串
     */
    public static String sm3(byte[] data) {
        return sm3(data, false);
    }

    /**
     * SM3摘要
     *
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return SM3摘要字符串
     */
    public static String sm3(byte[] data, boolean toUpperCase) {
        return digestHexString(ALGORITHM_SM3, data, toUpperCase);
    }

    /**
     * 生成指定算法摘要字符串
     *
     * @param algorithm   算法
     * @param data        数据
     * @param toUpperCase 是否大写
     * @return 摘要字符串
     */
    public String digestHexString(String algorithm, byte[] data, boolean toUpperCase) {
        if (data == null) {
            return null;
        }
        try {
            return HexUtil.encodeToString(MessageDigest.getInstance(algorithm).digest(data), toUpperCase);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + algorithm + "\"", e);
        }
    }

    /**
     * HmacMD5摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacMD5摘要字符串
     */
    public static String hmacMd5(String data, String key) {
        return hmacMd5(data, key, false);
    }

    /**
     * HmacMD5摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacMD5摘要字符串
     */
    public static String hmacMd5(String data, String key, boolean toUpperCase) {
        return hmacMd5(StringUtil.getBytes(data), key, toUpperCase);
    }

    /**
     * HmacMD5摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacMD5摘要字符串
     */
    public static String hmacMd5(byte[] data, String key) {
        return hmacMd5(data, key, false);
    }

    /**
     * HmacMD5摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacMD5摘要字符串
     */
    public static String hmacMd5(byte[] data, String key, boolean toUpperCase) {
        return digestHmacHexString(ALGORITHM_HMAC_MD5, data, key, toUpperCase);
    }

    /**
     * HmacSHA1摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA1摘要字符串
     */
    public static String hmacSha1(String data, String key) {
        return hmacSha1(data, key, false);
    }

    /**
     * HmacSHA1摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA1摘要字符串
     */
    public static String hmacSha1(String data, String key, boolean toUpperCase) {
        return hmacSha1(StringUtil.getBytes(data), key, toUpperCase);
    }

    /**
     * HmacSHA1摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA1摘要字符串
     */
    public static String hmacSha1(byte[] data, String key) {
        return hmacSha1(data, key, false);
    }

    /**
     * HmacSHA1摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA1摘要字符串
     */
    public static String hmacSha1(byte[] data, String key, boolean toUpperCase) {
        return digestHmacHexString(ALGORITHM_HMAC_SHA1, data, key, toUpperCase);
    }

    /**
     * HmacSHA224摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA224摘要字符串
     */
    public static String hmacSha224(String data, String key) {
        return hmacSha224(data, key, false);
    }

    /**
     * HmacSHA224摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA224摘要字符串
     */
    public static String hmacSha224(String data, String key, boolean toUpperCase) {
        return hmacSha224(StringUtil.getBytes(data), key, toUpperCase);
    }

    /**
     * HmacSHA224摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA224摘要字符串
     */
    public static String hmacSha224(byte[] data, String key) {
        return hmacSha224(data, key, false);
    }

    /**
     * HmacSHA224摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA224摘要字符串
     */
    public static String hmacSha224(byte[] data, String key, boolean toUpperCase) {
        return digestHmacHexString(ALGORITHM_HMAC_SHA224, data, key, toUpperCase);
    }

    /**
     * HmacSHA256摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA256摘要字符串
     */
    public static String hmacSha256(String data, String key) {
        return hmacSha256(data, key, false);
    }

    /**
     * HmacSHA256摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA256摘要字符串
     */
    public static String hmacSha256(String data, String key, boolean toUpperCase) {
        return hmacSha256(StringUtil.getBytes(data), key, toUpperCase);
    }

    /**
     * HmacSHA256摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA256摘要字符串
     */
    public static String hmacSha256(byte[] data, String key) {
        return hmacSha256(data, key, false);
    }

    /**
     * HmacSHA256摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA256摘要字符串
     */
    public static String hmacSha256(byte[] data, String key, boolean toUpperCase) {
        return digestHmacHexString(ALGORITHM_HMAC_SHA256, data, key, toUpperCase);
    }

    /**
     * HmacSHA384摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA384摘要字符串
     */
    public static String hmacSha384(String data, String key) {
        return hmacSha384(data, key, false);
    }

    /**
     * HmacSHA384摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA384摘要字符串
     */
    public static String hmacSha384(String data, String key, boolean toUpperCase) {
        return hmacSha384(StringUtil.getBytes(data), key, toUpperCase);
    }

    /**
     * HmacSHA384摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA384摘要字符串
     */
    public static String hmacSha384(byte[] data, String key) {
        return hmacSha384(data, key, false);
    }

    /**
     * HmacSHA384摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA384摘要字符串
     */
    public static String hmacSha384(byte[] data, String key, boolean toUpperCase) {
        return digestHmacHexString(ALGORITHM_HMAC_SHA384, data, key, toUpperCase);
    }

    /**
     * HmacSHA512摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA512摘要字符串
     */
    public static String hmacSha512(String data, String key) {
        return hmacSha512(data, key, false);
    }

    /**
     * HmacSHA512摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA512摘要字符串
     */
    public static String hmacSha512(String data, String key, boolean toUpperCase) {
        return hmacSha512(StringUtil.getBytes(data), key, toUpperCase);
    }

    /**
     * HmacSHA512摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSHA512摘要字符串
     */
    public static String hmacSha512(byte[] data, String key) {
        return hmacSha512(data, key, false);
    }

    /**
     * HmacSHA512摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSHA512摘要字符串
     */
    public static String hmacSha512(byte[] data, String key, boolean toUpperCase) {
        return digestHmacHexString(ALGORITHM_HMAC_SHA512, data, key, toUpperCase);
    }

    /**
     * HmacSM3摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSM3摘要字符串
     */
    public static String hmacSm3(String data, String key) {
        return hmacSm3(data, key, false);
    }

    /**
     * HmacSM3摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSM3摘要字符串
     */
    public static String hmacSm3(String data, String key, boolean toUpperCase) {
        return hmacSm3(StringUtil.getBytes(data), key, toUpperCase);
    }

    /**
     * HmacSM3摘要
     *
     * @param data 数据
     * @param key  密钥
     * @return HmacSM3摘要字符串
     */
    public static String hmacSm3(byte[] data, String key) {
        return hmacSm3(data, key, false);
    }

    /**
     * HmacSM3摘要
     *
     * @param data        数据
     * @param key         密钥
     * @param toUpperCase 是否大写
     * @return HmacSM3摘要字符串
     */
    public static String hmacSm3(byte[] data, String key, boolean toUpperCase) {
        return digestHmacHexString(ALGORITHM_HMAC_SM3, data, key, toUpperCase);
    }

    /**
     * 生成指定算法Hmac摘要字符串
     *
     * @param algorithm 算法
     * @param data      数据
     * @param key       密钥
     * @return Hmac摘要字符串
     */
    public static String digestHmacHexString(String algorithm, byte[] data, String key, boolean toUpperCase) {
        if (data == null) {
            return null;
        }

        SecretKey secretKey = new SecretKeySpec(StringUtil.getBytes(key), algorithm);
        try {
            Mac mac = Mac.getInstance(algorithm);
            mac.init(secretKey);
            return HexUtil.encodeToString(mac.doFinal(data), toUpperCase);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + algorithm + "\"", e);
        } catch (InvalidKeyException e) {
            throw new IllegalStateException("Invalid secret key: \"" + key + "\"", e);
        }
    }

}
